/**
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { Ref, ref } from 'vue';
import { DataGridProps, DataGridColumn } from '../data-grid.props';
import { UseFitColumn, UseGroupColumn } from './types';
import { ColumnRenderContext } from './use-column';

export function useFitColumn(
    props: DataGridProps,
    context: Ref<ColumnRenderContext>,
    useGroupColumnComposition: UseGroupColumn
): UseFitColumn {
    const { getGridHeaderCells } = useGroupColumnComposition;
    const defaultColumnWidth = 120;
    const defaultCheckboxWidth = 32;
    const showRowNumer = ref(props.rowNumber?.enable || false);
    const showRowCheckbox = ref(props.selection?.multiSelect || false);
    const sidebarColumnWidth = ref(
        0 + (showRowNumer.value ? props.rowNumber?.width || 0 : 0) + (showRowCheckbox.value ? defaultCheckboxWidth : 0)
    );

    const fitColumns = ref(props.columnOption?.fitColumns || false);
    const fitMode = ref((fitColumns.value && props.columnOption?.fitMode) || 'none');

    function calculateColumnWidthByPercentage(column: DataGridColumn, context: ColumnRenderContext, viewPortWidth: number) {
        const widthPerscent = parseInt(column.width as string, 10) / 100;
        const actualWidth = viewPortWidth * widthPerscent;
        return actualWidth;
    }

    function calculateColumnWidthNormally(column: DataGridColumn, context: ColumnRenderContext, viewPortWidth: number) {
        if (typeof column.width === 'string') {
            return calculateColumnWidthByPercentage(column, context, viewPortWidth);
        }
        return column.width || defaultColumnWidth;
    }

    function calculateColumnsSizeByPercentage(context: Ref<ColumnRenderContext>, viewPortWidth: number) {
        const columnsWithNumber: DataGridColumn[] = [];
        const columnsWithPercentage: DataGridColumn[] = [];
        let totalWidthWithNumber = 0;
        context.value.primaryColumns
            .filter((column: DataGridColumn) => column.visible)
            .forEach((visibleColumn: DataGridColumn) => {
                if (typeof visibleColumn.width === 'string') {
                    columnsWithPercentage.push(visibleColumn);
                } else {
                    visibleColumn.width = visibleColumn.width || defaultColumnWidth;
                    totalWidthWithNumber += visibleColumn.width;
                    columnsWithNumber.push(visibleColumn);
                }
            });
        const viewPortWidthNumber = viewPortWidth * (columnsWithNumber.length /
            context.value.primaryColumns.filter((column: DataGridColumn) => column.visible).length);
        columnsWithNumber.forEach((column: DataGridColumn) => {
            const actualWidth = viewPortWidthNumber * ((column.width as number) / totalWidthWithNumber);
            column.actualWidth = actualWidth;
            context.value.primaryColumnsWidth += column.actualWidth;
        });
        const restOfViewPortWidth = viewPortWidth - viewPortWidthNumber;
        columnsWithPercentage.forEach((column: DataGridColumn) => {
            const actualWidth = calculateColumnWidthByPercentage(column, context.value, restOfViewPortWidth);
            column.actualWidth = actualWidth;
            context.value.primaryColumnsWidth += column.actualWidth;
        });
    }

    function calculateColumnsSizeByAverage(context: Ref<ColumnRenderContext>, viewPortWidth: number) {
        const columnWidth = viewPortWidth / (context.value.primaryColumns.filter((column: DataGridColumn) => column.visible).length || 1);
        context.value.primaryColumns
            .filter((column: DataGridColumn) => column.visible)
            .forEach((visibleColumn: DataGridColumn) => {
                visibleColumn.actualWidth = columnWidth;
                context.value.primaryColumnsWidth += visibleColumn.actualWidth;
            });
    }

    function calculateColumnsSizeByExpand(context: Ref<ColumnRenderContext>, viewPortWidth: number) {
        const columnsWithNumber: DataGridColumn[] = [];
        const columnsWithPercentage: DataGridColumn[] = [];
        const preCalculateWidthMap = new WeakMap<DataGridColumn, number>();
        let totalWidthWithPercentage = 0;
        let totalWidthWithNumber = 0;
        context.value.primaryColumns
            .filter((column: DataGridColumn) => column.visible)
            .forEach((visibleColumn: DataGridColumn) => {
                if (typeof visibleColumn.width === 'string') {
                    const preCalculateWidth = calculateColumnWidthByPercentage(visibleColumn, context.value, viewPortWidth);
                    preCalculateWidthMap.set(visibleColumn, preCalculateWidth);
                    totalWidthWithPercentage += preCalculateWidth;
                    columnsWithPercentage.push(visibleColumn);
                } else {
                    visibleColumn.width = visibleColumn.width || defaultColumnWidth;
                    totalWidthWithNumber += visibleColumn.width;
                    columnsWithNumber.push(visibleColumn);
                }
            });
        const hasSpaceToExpand = viewPortWidth - totalWidthWithPercentage > totalWidthWithNumber;
        if (hasSpaceToExpand) {
            const restOfViewPortWidth = viewPortWidth - totalWidthWithPercentage;
            columnsWithPercentage.forEach((column: DataGridColumn) => {
                column.actualWidth = preCalculateWidthMap.get(column) || defaultColumnWidth;
                context.value.primaryColumnsWidth += column.actualWidth;
            });
            columnsWithNumber.forEach((column: DataGridColumn) => {
                column.actualWidth = restOfViewPortWidth * ((column.width as number) / totalWidthWithNumber);
                context.value.primaryColumnsWidth += column.actualWidth;
            });
        } else {
            calculateColumnsSizeByPercentage(context, viewPortWidth);
        }
    }

    function calculateColumnsSizeByNormal(context: Ref<ColumnRenderContext>, viewPortWidth: number) {
        context.value.primaryColumns
            .filter((column: DataGridColumn) => column.visible)
            .forEach((visibleColumn: DataGridColumn) => {
                visibleColumn.actualWidth = calculateColumnWidthNormally(visibleColumn, context.value, viewPortWidth);
                context.value.primaryColumnsWidth += visibleColumn.actualWidth;
            });
    }

    const columnWidthCalculators = {
        average: calculateColumnsSizeByAverage,
        expand: calculateColumnsSizeByExpand,
        none: calculateColumnsSizeByNormal,
        percentage: calculateColumnsSizeByPercentage
    };

    function calculateColumnHeaders(context: Ref<ColumnRenderContext>) {
        context.value.leftHeaderColumns = Array.from(getGridHeaderCells(context.value.leftColumns
            .filter((column: DataGridColumn) => column.visible)).values());
        context.value.primaryHeaderColumns = Array.from(getGridHeaderCells(context.value.primaryColumns
            .filter((column: DataGridColumn) => column.visible)).values());
        context.value.rightHeaderColumns = Array.from(getGridHeaderCells(context.value.rightColumns
            .filter((column: DataGridColumn) => column.visible)).values());
    }

    function calculateColumnsWidth(context: Ref<ColumnRenderContext>) {
        context.value.leftColumnsWidth = 0;
        context.value.primaryColumnsWidth = 0;
        context.value.rightColumnsWidth = 0;
        context.value.leftColumns.filter((column: DataGridColumn) => column.visible).forEach((column: DataGridColumn) => {
            context.value.leftColumnsWidth += column.actualWidth || 0;
        });
        context.value.primaryColumns.filter((column: DataGridColumn) => column.visible).forEach((column: DataGridColumn) => {
            context.value.primaryColumnsWidth += column.actualWidth || 0;
        });
        context.value.rightColumns.filter((column: DataGridColumn) => column.visible).forEach((column: DataGridColumn) => {
            context.value.rightColumnsWidth += column.actualWidth || 0;
        });
        context.value = Object.assign({}, context.value);
    }

    function fitRightFixedColumnPosition(context: Ref<ColumnRenderContext>, dataContentWidth: number, leftFixedColumnsWidth: number) {

    }

    function tryToArrangeRightColumnsWithPrimary(
        context: Ref<ColumnRenderContext>,
        dataContentWidth: number,
        viewPortWidthWithoutFixedRight: number
    ): boolean {
        let columnsWidth = 0;
        const originalPrimaryColumns = context.value.primaryColumns;
        const originalRightColumns = context.value.rightColumns;

        context.value.rightColumns.filter((column: DataGridColumn) => column.visible).forEach((column: DataGridColumn) => {
            column.actualWidth = calculateColumnWidthNormally(column, context.value, dataContentWidth);
            columnsWidth += column.actualWidth;
        });

        const leftViewPortWidth = viewPortWidthWithoutFixedRight - columnsWidth;

        const calculator = columnWidthCalculators[fitMode.value];
        if (calculator) {
            calculator(context, leftViewPortWidth);
        }

        if (context.value.primaryColumnsWidth + columnsWidth <= viewPortWidthWithoutFixedRight) {
            context.value.primaryColumns = [...originalPrimaryColumns, ...originalRightColumns];
            context.value.primaryColumnsWidth += columnsWidth;
            context.value.rightColumns = [];
            context.value.rightColumnsWidth = 0;
            return true;
        }
        const columnsMap = new Map<string, Array<DataGridColumn>>();
        columnsMap.set('primary', []);
        columnsMap.set('right', []);
        context.value.primaryColumns.reduce((columnsMap: Map<string, Array<DataGridColumn>>, currentColumn: DataGridColumn) => {
            if (currentColumn.fixed === 'right') {
                columnsMap.get('right')?.push(currentColumn);
            } else {
                columnsMap.get('primary')?.push(currentColumn);
            }
            return columnsMap;
        }, columnsMap);
        if (columnsMap.get('right')?.length) {
            context.value.primaryColumns = [...(columnsMap.get('primary') || [])];
            context.value.rightColumns = [...(columnsMap.get('right') || [])];
        }
        context.value.primaryColumnsWidth = 0;
        context.value.rightColumnsWidth = 0;
        return false;
    }

    function calculateColumnsSize(
        context: Ref<ColumnRenderContext>,
        gridContentElement: any,
        viewPortWidth: Ref<number>
    ) {
        if (gridContentElement) {
            context.value.leftColumnsWidth = 0;
            context.value.primaryColumnsWidth = 0;
            context.value.rightColumnsWidth = 0;

            const dataContentWidth = gridContentElement.clientWidth - sidebarColumnWidth.value;
            context.value.leftColumns.filter((column: DataGridColumn) => column.visible).forEach((column: DataGridColumn) => {
                column.actualWidth = calculateColumnWidthNormally(column, context.value, dataContentWidth);
                context.value.leftColumnsWidth += column.actualWidth;
            });

            const viewPortWidthWithoutFixedRight = dataContentWidth - context.value.leftColumnsWidth;

            if (tryToArrangeRightColumnsWithPrimary(context, dataContentWidth, viewPortWidthWithoutFixedRight)) {
                viewPortWidth.value = viewPortWidthWithoutFixedRight;
            } else {
                context.value.rightColumns.filter((column: DataGridColumn) => column.visible).forEach((column: DataGridColumn) => {
                    column.actualWidth = calculateColumnWidthNormally(column, context.value, dataContentWidth);
                    context.value.rightColumnsWidth += column.actualWidth;
                });

                const calculatedViewPortWidth = dataContentWidth - context.value.leftColumnsWidth - context.value.rightColumnsWidth;

                const calculator = columnWidthCalculators[fitMode.value];
                if (calculator) {
                    calculator(context, calculatedViewPortWidth);
                }

                viewPortWidth.value = calculatedViewPortWidth;
            }

            calculateColumnHeaders(context);
            context.value = Object.assign({}, context.value);
        }
    }

    return { calculateColumnHeaders, calculateColumnsSize, calculateColumnsWidth };
}
